﻿/* 
 * FlexContainer Server Control for ASP.NET 2.0
 * Copyright (C) 2009 Michael Paperin
 * All rights reserved.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as 
 * published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 */

using System;
using System.ComponentModel;
using System.IO;
using System.Web.UI;
using ASP = System.Web.UI.WebControls;
using NetServ.Net.Json;
using Gaia.WebWidgets;
using Gaia.WebWidgets.HtmlFormatting;

[assembly: WebResource("Ria.Web.Controls.Scripts.flashembed.min.js", "text/javascript")]
[assembly: WebResource("Ria.Web.Controls.Scripts.FlexContainer.js", "text/javascript")]

namespace Ria.Web.Controls
{
    /// <summary>
    /// FlexContainer wraps the Flex application into an ajaxified ASP.NET Server control that can easily be controlled from 
    /// the server. The control can be updated in callbacks and it disptaches useful events
    /// </summary>
    [DefaultProperty("FlexFile")]
    public class FlexContainer : GaiaControl, IAjaxControl
    {

        #region [FlexContainer private members and helper properties for serializing method calls]

        private ASP.Unit _width;
        private ASP.Unit _height;

        // ReSharper disable UnusedAutoPropertyAccessor.Local

        [AjaxSerializable("FlexCallObject")]
        private string AjaxFlexCallObject { get; set; }
        
        // ReSharper restore UnusedAutoPropertyAccessor.Local

        #endregion

        #region [FlexContainer Events]

        /// <summary>
        /// Event fires when the Flex sends object to container. 
        /// </summary>
        public event EventHandler<IncomingObjectEventArgs> IncomingObjectEvent;

        #endregion

        #region [FlexContainer Public Properties]

        [Category("FlexContainer Configuration")]
        [Description("If the user doesn't have Flash installed you can specify custom content here")]
        public string AlternativeContent { get; set; }

        /// <summary>
        /// Specify the Flex File to embed
        /// </summary>
        [Category("FlexContainer Configuration")]
        [Description("Specify the Flex File to embed")]
        public string FlexFile { get; set; }

        /// <summary>
        /// Show Flash Menu
        /// </summary>
        [Category("FlexContainer Configuration")]
        [Description("Enable or disable the Flash Context menu")]
        [DefaultValue(false)]
        public bool ShowFlashMenu { get; set; }

        /// <summary>
        /// Width of the Flash Player. This value is propagated to the embedded Flash Object. 
        /// </summary>
        [DefaultValue("400px")]
        [Category("FlexContainer Configuration")]
        [Description("Width of the FlexContainer")]
        public ASP.Unit Width
        {
            get
            {
                if (_width.IsEmpty)
                    return new ASP.Unit("400px");

                return _width;
            }
            set { _width = value; }
        }

        /// <summary>
        /// Height of the Flash Player. This value is propagated to the embedded Flash Object. 
        /// </summary>
        [DefaultValue("290px")]
        [Category("FlexContainer Configuration")]
        [Description("Height of the FlexContainer")]
        public ASP.Unit Height
        {
            get
            {
                if (_height.IsEmpty)
                    return new ASP.Unit("290px");
                return _height;
            }
            set { _height = value; }
        }

        /// <summary>
        /// Specify the initial Flex state string (the control to be shown) when Flex application is loaded
        /// </summary>
        [Category("FlexContainer Configuration")]
        [Description("Initial Flex state")]
        public string FlexState { get; set; }

        /// <summary>
        /// Specify the string to be sent to Felx application when its loaded
        /// String may contain JSON object
        /// </summary>
        [Category("FlexContainer Configuration")]
        [Description("Initial Flex string data")]
        public string InitialData { get; set; }

        #endregion

        #region [FlexContainer Public Methods]

        /// <summary>
        /// Call to Flex with JsonObject argument
        /// </summary>
        /// <param name="arg">arg</param>
        public void FlexCallObject(JsonObject arg)
        {
            if (!arg.ContainsKey("state") && !string.IsNullOrEmpty(FlexState))
                arg.Add("state", FlexState);
            AjaxFlexCallObject = arg.ToJsonString();
        }

        [Method]
        internal void ContainerCallObject(string data)
        {
            if (IncomingObjectEvent != null)
            {
                IncomingObjectEvent(this, new IncomingObjectEventArgs(JsonExtensions.ToJsonObject(data)));
            }
        }

        #endregion

        #region [Overriden base class and rendering]

        protected override void RenderControlHtml(XhtmlTagFactory create)
        {
            using (create.Div(ClientID).SetStyle("width:{0};height:{1}", Width, Height))
            {
                using (create.Div(ClientID + "_object"))
                {
                    if (!string.IsNullOrEmpty(AlternativeContent))
                    {
                        using (create.P().WriteContent(AlternativeContent)) { }
                    }
                }
            }
        }

        protected override void IncludeScriptFiles()
        {
            base.IncludeScriptFiles();
            Manager.Instance.AddInclusionOfFileFromResource("Ria.Web.Controls.Scripts.flashembed.min.js", typeof(FlexContainer), "RiaWebControls.FlexContainer.browserFinishedLoading");
            Manager.Instance.AddInclusionOfFileFromResource("Ria.Web.Controls.Scripts.FlexContainer.js", typeof(FlexContainer), "RiaWebControls.FlexContainer.browserFinishedLoading");
        }

        string IAjaxControl.GetScript()
        {
            return new RegisterControl("RiaWebControls.FlexContainer", ClientID)
                .AddProperty("name", ID)
                .AddProperty("flexSrc", FlexFile)
                .AddProperty("showFlashMenu", ShowFlashMenu)
                .AddProperty("width", Width.ToString())
                .AddProperty("height", Height.ToString())
                .AddProperty("cfg", GetConfigurationAsJson(), false)
                .ToString();
        }

        #endregion

        private string GetConfigurationAsJson()
        {
            var config = new JsonObject {{"containerId", ClientID}};
            if (!string.IsNullOrEmpty(FlexState))
                config.Add("state", FlexState);
            if (!string.IsNullOrEmpty(InitialData))
                config.Add("initialData", InitialData);
            return new JsonObject { { "config", config } }.ToJsonString();
        }
    }

    /// <summary>
    /// Event Arguments contain data object in JSON format
    /// </summary>
    public class IncomingObjectEventArgs : EventArgs
    {
        public JsonObject Data { get; set; }

        internal IncomingObjectEventArgs(JsonObject data)
        {
            Data = data;
        }
    }

    /// <summary>
    /// Extension methods for JSON handling
    /// </summary>
    public static class JsonExtensions
    {
        /// <summary>
        /// Writes a JSONType to a string and returns it
        /// </summary>
        /// <param name="type">JSONType</param>
        /// <returns>the object as a string</returns>
        public static string ToJsonString(this IJsonType type)
        {
            string json;
            using (var jsonWriter = new JsonWriter())
            {
                type.Write(jsonWriter);
                json = jsonWriter.ToString();
            }
            return json;
        }

        /// <summary>
        /// Converts JSON string to JsonObject and returns it
        /// </summary>
        /// <param name="json">JSON string</param>
        /// <returns>the JsonObject</returns>
        public static JsonObject ToJsonObject(string json)
        {
            JsonObject obj;
            using (var stringReader = new StringReader(json))
            {
                var parser = new JsonParser(stringReader, true);
                obj = parser.ParseObject();
            }
            return obj;
        }
    }
}
